/*
 * Copyright (c) 2011 Sapienza University of Rome.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the
 *   distribution.
 * - Neither the name of the Sapienza University of Rome nor the names of
 *   its contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL SAPIENZA
 * UNIVERSITY OF ROME OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * This component manages all Low Power Listening (LPL) operations of DISSense.
 * In particular it manages the out of synchronization states by randomly searching
 * for a node with a valid schedule and retrieving from it the synchronization msg.
 * This component also manages the Reset functionality of DISSense which enables a node
 * or the whole network to be reset.
 * @author Ugo Colesanti <colesanti@dis.uniroma1.it>
 * @version 1.02 (October 12, 2011)
 *
 */
module LplManagerC{
  provides{
	  interface LplResync ;
	  interface Reboot ;
  }
  uses{
	interface AMSend as LplSyncSend ;
	interface Receive as LplSyncReceive ;

	interface PacketAcknowledgements ;

#ifndef TOSSIM
	interface LowPowerListening ;
#endif
	interface Timer<TMilli> as LPLSyncMngmtTimer ;
	interface Timer<TMilli> as TryResyncTimer ;
	interface Timer<TMilli> as LPLResetBroadcastTimer ;
	interface Timer<TMilli> as WatchDogTimer ;


	interface ProtocolScheduler ;
	interface ScheduleManagement ;
	interface Random ;

	interface Leds ;

  }
}

implementation{
	enum{
		WATCHDOG_TIMER = 71680UL,		// 70 seconds
		RESET_BROADCAST_TIMER = 30720UL,// 30 seconds
		RESET_TIMER = 307200UL,			// 5 minutes
		BROADCAST_MESSAGES = 2,
	};

	static void sendSyncReply() ;
	static void sendSyncRequest() ;

	// Trickle-like timer functions and variables -> same as the ones implemented in CTP Routing Engine
	void resetInterval() ;
	void chooseAdvertiseTime() ;
	void decayInterval() ;
	void remainingInterval() ;
	uint32_t currentInterval = MIN_INTERVAL ;
	uint32_t trTime ;
	bool tHasPassed = FALSE ;


	// Reboot utility functions and variables
	void reboot() ;
	task void SendBroadcastReset() ;
	uint8_t reset_counter = BROADCAST_MESSAGES ;
	bool commandOff = FALSE ;
	bool rxOff = FALSE ;
	uint16_t reset_address = TOS_BCAST_ADDR ;


	// keep the state of the component (not a very nice solution...)
	bool resetting = FALSE ;
	bool sync_is_response ;

	// radio-related variables
	bool sendbusy ;
	message_t radiobuf ;


	/* Send a resynchronization request */
	static void sendSyncRequest(){

		  lplMsg_t *lplmsg ;
		  dbg("LplManager","Sending sync request\n") ;
//		  printf("Sending sync request\n") ; // was enabled in august
		  if(!sendbusy){
			  lplmsg = (lplMsg_t *) call LplSyncSend.getPayload(&radiobuf, sizeof(lplMsg_t));
			  if(lplmsg == NULL){
				  return;
			  }
			  lplmsg->type = LPLMSG_SYNC_REQUEST ;

#ifndef TOSSIM
//			  call LowPowerListening.setRxDutyCycle(&radiobuf,10) ;
			  call LowPowerListening.setRemoteWakeupInterval(&radiobuf,4463) ;
#endif

			  if (call LplSyncSend.send(AM_BROADCAST_ADDR,&radiobuf, sizeof(lplMsg_t)) == SUCCESS){
				  sendbusy = TRUE;
				  sync_is_response = FALSE ;
			  }

		  }
#ifndef TOSSIM
//		  printfflush() ; // was enabled in august
#endif
	}

	/* Send a synchronization message */
	static void sendSyncReply(){
		lplMsg_t *lplmsg ;

		if(!sendbusy){
			lplmsg = (lplMsg_t *) call LplSyncSend.getPayload(&radiobuf, sizeof(lplMsg_t));
			if(lplmsg == NULL){
				return ;
			}

			/* Set the current schedule and send the msg */
			lplmsg->type = LPLMSG_SYNC_REPLY ;
			lplmsg->schedule_info.current_sampling_period = call ScheduleManagement.getCurrentSamplingPeriod() ;
			lplmsg->schedule_info.current_schedule_gt = call ScheduleManagement.getCurrentGt() ;
			lplmsg->schedule_info.current_schedule_ri = call ScheduleManagement.getNextRi() ;
			lplmsg->schedule_info.current_schedule_dci = call ScheduleManagement.getCurrentDci() ;
			lplmsg->schedule_info.current_schedule_skip = call ScheduleManagement.getCurrentSkip() ;
			lplmsg->schedule_info.next_sampling_period = call ScheduleManagement.getNextSamplingPeriod() ;
			lplmsg->schedule_info.time_to_next_sampling_period = call ScheduleManagement.getTimeToNextSP() ;
			lplmsg->schedule_info.next_sampling_period_fired = call ScheduleManagement.getNextSamplingPeriodFired() ;

//			printf("Sending sync msg - Type: %lu, S. Period: %u, Gt:%u, Ri: %u, Dci: %u, Skip: %u, Next S.P.: %u\n",LPLMSG_SYNC_REPLY,lplmsg->current_sampling_period ,lplmsg->current_schedule_gt,lplmsg->next_schedule_ri,lplmsg->current_schedule_dci,lplmsg->current_schedule_skip,lplmsg->next_sampling_period) ;// was enabled in august
			dbg("LplManager","Sending sync msg - Type: %u, S. Period: %u, Gt:%u, Ri: %u, Dci: %u, Skip: %u, Next S.P.: %u\n",LPLMSG_SYNC_REPLY,lplmsg->schedule_info.current_sampling_period ,lplmsg->schedule_info.current_schedule_gt,lplmsg->schedule_info.current_schedule_ri,lplmsg->schedule_info.current_schedule_dci,lplmsg->schedule_info.current_schedule_skip,lplmsg->schedule_info.next_sampling_period) ;

#ifndef TOSSIM
//			call LowPowerListening.setRxDutyCycle(&radiobuf,10000) ; // transmit to a 100% duty cycled radio
			call LowPowerListening.setRemoteWakeupInterval(&radiobuf,0) ;
#endif
			if (call LplSyncSend.send(AM_BROADCAST_ADDR,&radiobuf, sizeof(lplMsg_t)) == SUCCESS){
				sync_is_response = TRUE ;
				sendbusy = TRUE;
			}
		}
#ifndef TOSSIM
//		printfflush() ; // was enabled in august
#endif
	}

	command void LplResync.beginResyncProcess(){
		if(commandOff) return ;
		dbg("LplManager","Begin resync process...\n") ;
//		printf("Begin resync process...\n") ; // was enabled in august
#ifndef TOSSIM
//		printfflush() ; // was enabled in august
#endif
		resetInterval() ;
	}

	command void LplResync.stopResyncProcess(){
		if(commandOff) return ;
//		printf("Stop resync process...\n") ; // was enabled in august
#ifndef TOSSIM
//		printfflush() ; // was enabled in august
#endif
		call TryResyncTimer.stop() ;
	}

	command void Reboot.immediateReboot(){
		reboot() ;
	}

	command void Reboot.delayedReboot(){
		atomic{
			// stop timers
			call LPLSyncMngmtTimer.stop() ;
			call TryResyncTimer.stop() ;

			// disable events
			commandOff = TRUE ;
			rxOff = TRUE ;

			// Now the component is isolated, start the watchdog timer and the reset process.
			call Leds.led1On() ;
			call Leds.led2On() ;
			call WatchDogTimer.startOneShot(RESET_TIMER) ;
		}
	}

	command void Reboot.propagateAndReboot(){
		atomic{
			// stop timers
			call LPLSyncMngmtTimer.stop() ;
			call TryResyncTimer.stop() ;

			// disable events
			commandOff = TRUE ;
			rxOff = TRUE ;

			// Now the component is isolated, start the watchdog timer and the reset process.
			resetting = TRUE ;
			call Leds.led2On() ;
			call LPLResetBroadcastTimer.startOneShot(RESET_BROADCAST_TIMER+(call Random.rand16() % RESET_BROADCAST_TIMER)) ;
			call WatchDogTimer.startOneShot(WATCHDOG_TIMER) ;
		}
	}

	command void Reboot.propagate(uint16_t addr){
		// stop timers
		call LPLSyncMngmtTimer.stop() ;
		call TryResyncTimer.stop() ;

		// disable events
		commandOff = TRUE ;
		rxOff = TRUE ;

		// Now the component is isolated, start the watchdog timer and the reset process.
		resetting = TRUE ;
		reset_address = addr ;
		call Leds.led1On() ;
		call LPLResetBroadcastTimer.startOneShot(RESET_BROADCAST_TIMER+(call Random.rand16() % RESET_BROADCAST_TIMER)) ;
	}



	task void SendBroadcastReset(){
		lplMsg_t *lplmsg ;
		if(!sendbusy){
		  lplmsg = (lplMsg_t *) call LplSyncSend.getPayload(&radiobuf, sizeof(lplMsg_t));
		  if(lplmsg == NULL){
			  return ;
		  }

		  lplmsg->type = LPLMSG_BROADCAST_RESET ;
		  lplmsg->address = reset_address ;
#ifndef TOSSIM
//		  call LowPowerListening.setRxDutyCycle(&radiobuf,10) ; // transmit to a 0.1% duty cycled radio
		  call LowPowerListening.setRemoteWakeupInterval(&radiobuf,4463) ;
#endif
		  if (call LplSyncSend.send(AM_BROADCAST_ADDR,&radiobuf, sizeof(lplMsg_t)) == SUCCESS){
			  sendbusy = TRUE;
		  }
	  }

	}

	/* Reset timer has fired, schedule a reset msg transmission */
	event void LPLResetBroadcastTimer.fired(){
		if(reset_counter == 0){
				call WatchDogTimer.startOneShot(RESET_TIMER) ;
		}
		else{
			post SendBroadcastReset() ;
			reset_counter-- ;
		}
	}

	/* Something wrong happened, reset */
	event void WatchDogTimer.fired(){
		if(reset_address == TOS_BCAST_ADDR){
			reboot() ;
		}
		else{
			// reactivate commands and events:
			reset_address = TOS_BCAST_ADDR ;
			reset_counter = BROADCAST_MESSAGES ;
			resetting = FALSE ;

			commandOff = FALSE ;
			rxOff = FALSE ;

			call Leds.led1Off() ;
			call Leds.led2Off() ;
		}


	}

	event void LPLSyncMngmtTimer.fired(){
		  sendSyncReply() ;
	}

	event void TryResyncTimer.fired(){
		  if(!tHasPassed){
			  //send sync request but only if it's safe to send, otherwise skip this round
			  if(call ProtocolScheduler.allowLplSyncRequest()){
				  sendSyncRequest() ;
			  }
			  remainingInterval() ;
		  }
		  else decayInterval() ;
	}




	/**
	 * Lpl message received:
	 *
	 * - If it is a request, check if I'm in sync AND if there is enough time to transmit a reply and if I'm not currently in active state, then send a sync message.
	 * If I'm not in sync OR I have not enough time, just ignore the request.
	 *
	 * - If it is a sync message, signal it to ManagerC such that it can resynchronize
	 *
	 * - If it is a reset message, check if it is a broadcast or unicast request and take a decision accordingly
	 *
	 * -
	 */

	event message_t* LplSyncReceive.receive(message_t* msg, void* payload, uint8_t len) {
		uint16_t rnd ;
		lplMsg_t* lplmsgRx = (lplMsg_t*) payload ;

		if(rxOff) return msg; // reception disabled (I'm resetting)

		if(sizeof(lplMsg_t) == len){

			if(lplmsgRx->type == LPLMSG_SYNC_REQUEST){
//				printf("Received sync request...\n") ; // was enabled in august

				// need to wait the LPL tx time (worst case -> whole sleep period) plus a random backoff
#ifndef TOSSIM
				rnd = /*call LowPowerListening.dutyCycleToSleepInterval(10)*/ 4463 + call Random.rand16() % LPL_RESPONSE_BACKOFF ;
#else
				rnd = 4463 + call Random.rand16() % LPL_RESPONSE_BACKOFF ;
#endif
//				printf("Received sync request, reply in %u ms\n",rnd) ; // was enabled in august

				if(call ProtocolScheduler.allowLplSyncResponse(rnd+15) /*call ProtocolScheduler.isSafeIntervalFor(rnd+15)*/){
//					printf("Allowed to reply\n") ; // was enabled in august
					call LPLSyncMngmtTimer.startOneShot(rnd) ;
				}
				// else ignore

			}
			else if(lplmsgRx->type == LPLMSG_SYNC_REPLY){
//				printf("Received Sync reply\n") ; // was enabled in august
				signal LplResync.receivedSync(lplmsgRx) ;
			}
			else if(lplmsgRx->type == LPLMSG_BROADCAST_RESET){
				// check if it is a unicast address or broadcast
				// broadcast: must propagate the msg then reboot
				// unicast: can reboot but must wait that the msg get dissolved
				if(lplmsgRx->address == TOS_NODE_ID) call Reboot.delayedReboot() ;
				else if(lplmsgRx->address == TOS_BCAST_ADDR) call Reboot.propagateAndReboot() ;
				else call Reboot.propagate(lplmsgRx->address) ; // propagate and do nothing

			}
			else if(lplmsgRx->type == LPLMSG_UNICAST_RESET){
				call Reboot.immediateReboot() ; // ok, reboot directly
			}
		}
#ifndef TOSSIM
//		printfflush() ; // was enabled in august
#endif
		return msg ;
	}

	/**
	 * Node reset function
	 */
	void reboot() {
	  WDTCTL = WDT_ARST_1_9;
	  while(1);
	}

	/**
	 * Once the Lpl packet has been sent, check the state then:
	 *
	 * - If the component was managing some kind of reboot procedure, call LplResetBroadcastTimer and update WatchDogTimer
	 *
	 * - If the component was replying to a sync request, signal syncRespondeSendDone to the ManagerC
	 *
	 * - If the component was requesting a sync message, signal syncRequestSendDone to ManagerC
	 */
	event void LplSyncSend.sendDone(message_t* msg, error_t error) {
		if(resetting){
			call LPLResetBroadcastTimer.startOneShot(RESET_BROADCAST_TIMER+(call Random.rand16() % RESET_BROADCAST_TIMER)) ;
			call WatchDogTimer.startOneShot(WATCHDOG_TIMER) ;
		}
		else{
			if(sync_is_response) call ProtocolScheduler.syncResponseSendDone() ;
			else call ProtocolScheduler.syncRequestSendDone() ;
		}
		sendbusy = FALSE ;
	}


// Trickle-like functions (same code as CtpRoutingEngineC) -------------------------------------------------------
	void resetInterval() {
		currentInterval = MIN_INTERVAL;
		chooseAdvertiseTime();
	}

	void decayInterval() {
	  currentInterval *= 2;
	  if (currentInterval > MAX_INTERVAL) {
		currentInterval = MAX_INTERVAL ;
	  }
	  chooseAdvertiseTime();
	}

	void remainingInterval() {
	 uint32_t remaining = currentInterval;
	 remaining -= trTime ;
	 tHasPassed = TRUE;
	 call TryResyncTimer.startOneShot(remaining);
	 dbg("LplManager","tHasPassed: TRUE , fire in: %lu\n",trTime) ;
	}

	void chooseAdvertiseTime() {
	 trTime = currentInterval;
	 trTime /= 2;
	 trTime += call Random.rand32() % trTime ;
	 tHasPassed = FALSE ;
	 call TryResyncTimer.stop();
	 call TryResyncTimer.startOneShot(trTime);
	 dbg("LplManager","tHasPassed: FALSE , fire in: %lu\n",trTime) ;
	}
// ----------------------------------------------------------------------------------------------------------------
}
